1   /*
2    * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  package com.sun.java.swing.plaf.gtk;
26  
27  import sun.swing.SwingUtilities2;
28  import com.sun.java.swing.plaf.gtk.GTKConstants.ArrowType;
29  import com.sun.java.swing.plaf.gtk.GTKConstants.ShadowType;
30  
31  import javax.swing.plaf.ColorUIResource;
32  import javax.swing.plaf.synth.*;
33  
34  import java.awt.*;
35  import java.awt.geom.*;
36  import java.awt.image.*;
37  import java.io.*;
38  import java.net.*;
39  import java.security.*;
40  import java.util.*;
41  
42  import javax.swing.*;
43  import javax.swing.border.*;
44  
45  import javax.xml.parsers.*;
46  import org.xml.sax.SAXException;
47  import org.w3c.dom.*;
48  
49  /**
50   */
51  class Metacity implements SynthConstants {
52      // Tutorial:
53      // http://developer.gnome.org/doc/tutorials/metacity/metacity-themes.html
54  
55      // Themes:
56      // http://art.gnome.org/theme_list.php?category=metacity
57  
58      static Metacity INSTANCE;
59  
60      private static final String[] themeNames = {
61          getUserTheme(),
62          "blueprint",
63          "Bluecurve",
64          "Crux",
65          "SwingFallbackTheme"
66      };
67  
68      static {
69          for (String themeName : themeNames) {
70              if (themeName != null) {
71              try {
72                  INSTANCE = new Metacity(themeName);
73              } catch (FileNotFoundException ex) {
74              } catch (IOException ex) {
75                  logError(themeName, ex);
76              } catch (ParserConfigurationException ex) {
77                  logError(themeName, ex);
78              } catch (SAXException ex) {
79                  logError(themeName, ex);
80              }
81              }
82              if (INSTANCE != null) {
83              break;
84              }
85          }
86          if (INSTANCE == null) {
87              throw new Error("Could not find any installed metacity theme, and fallback failed");
88          }
89      }
90  
91      private static boolean errorLogged = false;
92      private static DocumentBuilder documentBuilder;
93      private static Document xmlDoc;
94      private static String userHome;
95  
96      private Node frame_style_set;
97      private Map<String, Object> frameGeometry;
98      private Map<String, Map<String, Object>> frameGeometries;
99  
100     private LayoutManager titlePaneLayout = new TitlePaneLayout();
101 
102     private ColorizeImageFilter imageFilter = new ColorizeImageFilter();
103     private URL themeDir = null;
104     private SynthContext context;
105     private String themeName;
106 
107     private ArithmeticExpressionEvaluator aee = new ArithmeticExpressionEvaluator();
108     private Map<String, Integer> variables;
109 
110     // Reusable clip shape object
111     private RoundRectClipShape roundedClipShape;
112 
113     protected Metacity(String themeName) throws IOException, ParserConfigurationException, SAXException {
114         this.themeName = themeName;
115         themeDir = getThemeDir(themeName);
116         if (themeDir != null) {
117             URL themeURL = new URL(themeDir, "metacity-theme-1.xml");
118             xmlDoc = getXMLDoc(themeURL);
119             if (xmlDoc == null) {
120                 throw new IOException(themeURL.toString());
121             }
122         } else {
123             throw new FileNotFoundException(themeName);
124         }
125 
126         // Initialize constants
127         variables = new HashMap<String, Integer>();
128         NodeList nodes = xmlDoc.getElementsByTagName("constant");
129         int n = nodes.getLength();
130         for (int i = 0; i < n; i++) {
131             Node node = nodes.item(i);
132             String name = getStringAttr(node, "name");
133             if (name != null) {
134                 String value = getStringAttr(node, "value");
135                 if (value != null) {
136                     try {
137                         variables.put(name, Integer.parseInt(value));
138                     } catch (NumberFormatException ex) {
139                         logError(themeName, ex);
140                         // Ignore bad value
141                     }
142                 }
143             }
144         }
145 
146         // Cache frame geometries
147         frameGeometries = new HashMap<String, Map<String, Object>>();
148         nodes = xmlDoc.getElementsByTagName("frame_geometry");
149         n = nodes.getLength();
150         for (int i = 0; i < n; i++) {
151             Node node = nodes.item(i);
152             String name = getStringAttr(node, "name");
153             if (name != null) {
154                 HashMap<String, Object> gm = new HashMap<String, Object>();
155                 frameGeometries.put(name, gm);
156 
157                 String parentGM = getStringAttr(node, "parent");
158                 if (parentGM != null) {
159                     gm.putAll(frameGeometries.get(parentGM));
160                 }
161 
162                 gm.put("has_title",
163                        Boolean.valueOf(getBooleanAttr(node, "has_title",            true)));
164                 gm.put("rounded_top_left",
165                        Boolean.valueOf(getBooleanAttr(node, "rounded_top_left",     false)));
166                 gm.put("rounded_top_right",
167                        Boolean.valueOf(getBooleanAttr(node, "rounded_top_right",    false)));
168                 gm.put("rounded_bottom_left",
169                        Boolean.valueOf(getBooleanAttr(node, "rounded_bottom_left",  false)));
170                 gm.put("rounded_bottom_right",
171                        Boolean.valueOf(getBooleanAttr(node, "rounded_bottom_right", false)));
172 
173                 NodeList childNodes = node.getChildNodes();
174                 int nc = childNodes.getLength();
175                 for (int j = 0; j < nc; j++) {
176                     Node child = childNodes.item(j);
177                     if (child.getNodeType() == Node.ELEMENT_NODE) {
178                         name = child.getNodeName();
179                         Object value = null;
180                         if ("distance".equals(name)) {
181                             value = Integer.valueOf(getIntAttr(child, "value", 0));
182                         } else if ("border".equals(name)) {
183                             value = new Insets(getIntAttr(child, "top", 0),
184                                                getIntAttr(child, "left", 0),
185                                                getIntAttr(child, "bottom", 0),
186                                                getIntAttr(child, "right", 0));
187                         } else if ("aspect_ratio".equals(name)) {
188                             value = new Float(getFloatAttr(child, "value", 1.0F));
189                         } else {
190                             logError(themeName, "Unknown Metacity frame geometry value type: "+name);
191                         }
192                         String childName = getStringAttr(child, "name");
193                         if (childName != null && value != null) {
194                             gm.put(childName, value);
195                         }
196                     }
197                 }
198             }
199         }
200         frameGeometry = frameGeometries.get("normal");
201     }
202 
203 
204     public static LayoutManager getTitlePaneLayout() {
205         return INSTANCE.titlePaneLayout;
206     }
207 
208     private Shape getRoundedClipShape(int x, int y, int w, int h,
209                                       int arcw, int arch, int corners) {
210         if (roundedClipShape == null) {
211             roundedClipShape = new RoundRectClipShape();
212         }
213         roundedClipShape.setRoundedRect(x, y, w, h, arcw, arch, corners);
214 
215         return roundedClipShape;
216     }
217 
218     void paintButtonBackground(SynthContext context, Graphics g, int x, int y, int w, int h) {
219         updateFrameGeometry(context);
220 
221         this.context = context;
222         JButton button = (JButton)context.getComponent();
223         String buttonName = button.getName();
224         int buttonState = context.getComponentState();
225 
226         JComponent titlePane = (JComponent)button.getParent();
227         Container titlePaneParent = titlePane.getParent();
228 
229         JInternalFrame jif;
230         if (titlePaneParent instanceof JInternalFrame) {
231             jif = (JInternalFrame)titlePaneParent;
232         } else if (titlePaneParent instanceof JInternalFrame.JDesktopIcon) {
233             jif = ((JInternalFrame.JDesktopIcon)titlePaneParent).getInternalFrame();
234         } else {
235             return;
236         }
237 
238         boolean active = jif.isSelected();
239         button.setOpaque(false);
240 
241         String state = "normal";
242         if ((buttonState & PRESSED) != 0) {
243             state = "pressed";
244         } else if ((buttonState & MOUSE_OVER) != 0) {
245             state = "prelight";
246         }
247 
248         String function = null;
249         String location = null;
250         boolean left_corner  = false;
251         boolean right_corner = false;
252 
253 
254         if (buttonName == "InternalFrameTitlePane.menuButton") {
255             function = "menu";
256             location = "left_left";
257             left_corner = true;
258         } else if (buttonName == "InternalFrameTitlePane.iconifyButton") {
259             function = "minimize";
260             int nButtons = ((jif.isIconifiable() ? 1 : 0) +
261                             (jif.isMaximizable() ? 1 : 0) +
262                             (jif.isClosable() ? 1 : 0));
263             right_corner = (nButtons == 1);
264             switch (nButtons) {
265               case 1: location = "right_right"; break;
266               case 2: location = "right_middle"; break;
267               case 3: location = "right_left"; break;
268             }
269         } else if (buttonName == "InternalFrameTitlePane.maximizeButton") {
270             function = "maximize";
271             right_corner = !jif.isClosable();
272             location = jif.isClosable() ? "right_middle" : "right_right";
273         } else if (buttonName == "InternalFrameTitlePane.closeButton") {
274             function = "close";
275             right_corner = true;
276             location = "right_right";
277         }
278 
279         Node frame = getNode(frame_style_set, "frame", new String[] {
280             "focus", (active ? "yes" : "no"),
281             "state", (jif.isMaximum() ? "maximized" : "normal")
282         });
283 
284         if (function != null && frame != null) {
285             Node frame_style = getNode("frame_style", new String[] {
286                 "name", getStringAttr(frame, "style")
287             });
288             if (frame_style != null) {
289                 Shape oldClip = g.getClip();
290                 if ((right_corner && getBoolean("rounded_top_right", false)) ||
291                     (left_corner  && getBoolean("rounded_top_left", false))) {
292 
293                     Point buttonLoc = button.getLocation();
294                     if (right_corner) {
295                         g.setClip(getRoundedClipShape(0, 0, w, h,
296                                                       12, 12, RoundRectClipShape.TOP_RIGHT));
297                     } else {
298                         g.setClip(getRoundedClipShape(0, 0, w, h,
299                                                       11, 11, RoundRectClipShape.TOP_LEFT));
300                     }
301 
302                     Rectangle clipBounds = oldClip.getBounds();
303                     g.clipRect(clipBounds.x, clipBounds.y,
304                                clipBounds.width, clipBounds.height);
305                 }
306                 drawButton(frame_style, location+"_background", state, g, w, h, jif);
307                 drawButton(frame_style, function, state, g, w, h, jif);
308                 g.setClip(oldClip);
309             }
310         }
311     }
312 
313     protected void drawButton(Node frame_style, String function, String state,
314                             Graphics g, int w, int h, JInternalFrame jif) {
315         Node buttonNode = getNode(frame_style, "button",
316                                   new String[] { "function", function, "state", state });
317         if (buttonNode == null && !state.equals("normal")) {
318             buttonNode = getNode(frame_style, "button",
319                                  new String[] { "function", function, "state", "normal" });
320         }
321         if (buttonNode != null) {
322             Node draw_ops;
323             String draw_ops_name = getStringAttr(buttonNode, "draw_ops");
324             if (draw_ops_name != null) {
325                 draw_ops = getNode("draw_ops", new String[] { "name", draw_ops_name });
326             } else {
327                 draw_ops = getNode(buttonNode, "draw_ops", null);
328             }
329             variables.put("width",  w);
330             variables.put("height", h);
331             draw(draw_ops, g, jif);
332         }
333     }
334 
335     void paintFrameBorder(SynthContext context, Graphics g, int x0, int y0, int width, int height) {
336         updateFrameGeometry(context);
337 
338         this.context = context;
339         JComponent comp = context.getComponent();
340         JComponent titlePane = findChild(comp, "InternalFrame.northPane");
341 
342         if (titlePane == null) {
343             return;
344         }
345 
346         JInternalFrame jif = null;
347         if (comp instanceof JInternalFrame) {
348             jif = (JInternalFrame)comp;
349         } else if (comp instanceof JInternalFrame.JDesktopIcon) {
350             jif = ((JInternalFrame.JDesktopIcon)comp).getInternalFrame();
351         } else {
352             assert false : "component is not JInternalFrame or JInternalFrame.JDesktopIcon";
353             return;
354         }
355 
356         boolean active = jif.isSelected();
357         Font oldFont = g.getFont();
358         g.setFont(titlePane.getFont());
359         g.translate(x0, y0);
360 
361         Rectangle titleRect = calculateTitleArea(jif);
362         JComponent menuButton = findChild(titlePane, "InternalFrameTitlePane.menuButton");
363 
364         Icon frameIcon = jif.getFrameIcon();
365         variables.put("mini_icon_width",
366                       (frameIcon != null) ? frameIcon.getIconWidth()  : 0);
367         variables.put("mini_icon_height",
368                       (frameIcon != null) ? frameIcon.getIconHeight() : 0);
369         variables.put("title_width",  calculateTitleTextWidth(g, jif));
370         FontMetrics fm = SwingUtilities2.getFontMetrics(jif, g);
371         variables.put("title_height", fm.getAscent() + fm.getDescent());
372 
373         // These don't seem to apply here, but the Galaxy theme uses them. Not sure why.
374         variables.put("icon_width",  32);
375         variables.put("icon_height", 32);
376 
377         if (frame_style_set != null) {
378             Node frame = getNode(frame_style_set, "frame", new String[] {
379                 "focus", (active ? "yes" : "no"),
380                 "state", (jif.isMaximum() ? "maximized" : "normal")
381             });
382 
383             if (frame != null) {
384                 Node frame_style = getNode("frame_style", new String[] {
385                     "name", getStringAttr(frame, "style")
386                 });
387                 if (frame_style != null) {
388                     Shape oldClip = g.getClip();
389                     boolean roundTopLeft     = getBoolean("rounded_top_left",     false);
390                     boolean roundTopRight    = getBoolean("rounded_top_right",    false);
391                     boolean roundBottomLeft  = getBoolean("rounded_bottom_left",  false);
392                     boolean roundBottomRight = getBoolean("rounded_bottom_right", false);
393 
394                     if (roundTopLeft || roundTopRight || roundBottomLeft || roundBottomRight) {
395                         jif.setOpaque(false);
396 
397                         g.setClip(getRoundedClipShape(0, 0, width, height, 12, 12,
398                                         (roundTopLeft     ? RoundRectClipShape.TOP_LEFT     : 0) |
399                                         (roundTopRight    ? RoundRectClipShape.TOP_RIGHT    : 0) |
400                                         (roundBottomLeft  ? RoundRectClipShape.BOTTOM_LEFT  : 0) |
401                                         (roundBottomRight ? RoundRectClipShape.BOTTOM_RIGHT : 0)));
402                     }
403 
404                     Rectangle clipBounds = oldClip.getBounds();
405                     g.clipRect(clipBounds.x, clipBounds.y,
406                                clipBounds.width, clipBounds.height);
407 
408                     int titleHeight = titlePane.getHeight();
409 
410                     boolean minimized = jif.isIcon();
411                     Insets insets = getBorderInsets(context, null);
412 
413                     int leftTitlebarEdge   = getInt("left_titlebar_edge");
414                     int rightTitlebarEdge  = getInt("right_titlebar_edge");
415                     int topTitlebarEdge    = getInt("top_titlebar_edge");
416                     int bottomTitlebarEdge = getInt("bottom_titlebar_edge");
417 
418                     if (!minimized) {
419                         drawPiece(frame_style, g, "entire_background",
420                                   0, 0, width, height, jif);
421                     }
422                     drawPiece(frame_style, g, "titlebar",
423                               0, 0, width, titleHeight, jif);
424                     drawPiece(frame_style, g, "titlebar_middle",
425                               leftTitlebarEdge, topTitlebarEdge,
426                               width - leftTitlebarEdge - rightTitlebarEdge,
427                               titleHeight - topTitlebarEdge - bottomTitlebarEdge,
428                               jif);
429                     drawPiece(frame_style, g, "left_titlebar_edge",
430                               0, 0, leftTitlebarEdge, titleHeight, jif);
431                     drawPiece(frame_style, g, "right_titlebar_edge",
432                               width - rightTitlebarEdge, 0,
433                               rightTitlebarEdge, titleHeight, jif);
434                     drawPiece(frame_style, g, "top_titlebar_edge",
435                               0, 0, width, topTitlebarEdge, jif);
436                     drawPiece(frame_style, g, "bottom_titlebar_edge",
437                               0, titleHeight - bottomTitlebarEdge,
438                               width, bottomTitlebarEdge, jif);
439                     drawPiece(frame_style, g, "title",
440                               titleRect.x, titleRect.y, titleRect.width, titleRect.height, jif);
441                     if (!minimized) {
442                         drawPiece(frame_style, g, "left_edge",
443                                   0, titleHeight, insets.left, height-titleHeight, jif);
444                         drawPiece(frame_style, g, "right_edge",
445                                   width-insets.right, titleHeight, insets.right, height-titleHeight, jif);
446                         drawPiece(frame_style, g, "bottom_edge",
447                                   0, height - insets.bottom, width, insets.bottom, jif);
448                         drawPiece(frame_style, g, "overlay",
449                                   0, 0, width, height, jif);
450                     }
451                     g.setClip(oldClip);
452                 }
453             }
454         }
455         g.translate(-x0, -y0);
456         g.setFont(oldFont);
457     }
458 
459 
460 
461     private static class Privileged implements PrivilegedAction<Object> {
462         private static int GET_THEME_DIR  = 0;
463         private static int GET_USER_THEME = 1;
464         private static int GET_IMAGE      = 2;
465         private int type;
466         private Object arg;
467 
468         public Object doPrivileged(int type, Object arg) {
469             this.type = type;
470             this.arg = arg;
471             return AccessController.doPrivileged(this);
472         }
473 
474         public Object run() {
475             if (type == GET_THEME_DIR) {
476                 String sep = File.separator;
477                 String[] dirs = new String[] {
478                     userHome + sep + ".themes",
479                     System.getProperty("swing.metacitythemedir"),
480                     "/usr/share/themes",
481                     "/usr/gnome/share/themes",  // Debian/Redhat/Solaris
482                     "/opt/gnome2/share/themes"  // SuSE
483                 };
484 
485                 URL themeDir = null;
486                 for (int i = 0; i < dirs.length; i++) {
487                     // System property may not be set so skip null directories.
488                     if (dirs[i] == null) {
489                         continue;
490                     }
491                     File dir =
492                         new File(dirs[i] + sep + arg + sep + "metacity-1");
493                     if (new File(dir, "metacity-theme-1.xml").canRead()) {
494                         try {
495                             themeDir = dir.toURI().toURL();
496                         } catch (MalformedURLException ex) {
497                             themeDir = null;
498                         }
499                         break;
500                     }
501                 }
502                 if (themeDir == null) {
503                     String filename = "resources/metacity/" + arg +
504                         "/metacity-1/metacity-theme-1.xml";
505                     URL url = getClass().getResource(filename);
506                     if (url != null) {
507                         String str = url.toString();
508                         try {
509                             themeDir = new URL(str.substring(0, str.lastIndexOf('/'))+"/");
510                         } catch (MalformedURLException ex) {
511                             themeDir = null;
512                         }
513                     }
514                 }
515                 return themeDir;
516             } else if (type == GET_USER_THEME) {
517                 try {
518                     // Set userHome here because we need the privilege
519                     userHome = System.getProperty("user.home");
520 
521                     String theme = System.getProperty("swing.metacitythemename");
522                     if (theme != null) {
523                         return theme;
524                     }
525                     // Note: this is a small file (< 1024 bytes) so it's not worth
526                     // starting an XML parser or even to use a buffered reader.
527                     URL url = new URL(new File(userHome).toURI().toURL(),
528                                       ".gconf/apps/metacity/general/%25gconf.xml");
529                     // Pending: verify character encoding spec for gconf
530                     Reader reader = new InputStreamReader(url.openStream(), "ISO-8859-1");
531                     char[] buf = new char[1024];
532                     StringBuffer strBuf = new StringBuffer();
533                     int n;
534                     while ((n = reader.read(buf)) >= 0) {
535                         strBuf.append(buf, 0, n);
536                     }
537                     reader.close();
538                     String str = strBuf.toString();
539                     if (str != null) {
540                         String strLowerCase = str.toLowerCase();
541                         int i = strLowerCase.indexOf("<entry name=\"theme\"");
542                         if (i >= 0) {
543                             i = strLowerCase.indexOf("<stringvalue>", i);
544                             if (i > 0) {
545                                 i += "<stringvalue>".length();
546                                 int i2 = str.indexOf("<", i);
547                                 return str.substring(i, i2);
548                             }
549                         }
550                     }
551                 } catch (MalformedURLException ex) {
552                     // OK to just ignore. We'll use a fallback theme.
553                 } catch (IOException ex) {
554                     // OK to just ignore. We'll use a fallback theme.
555                 }
556                 return null;
557             } else if (type == GET_IMAGE) {
558                 return new ImageIcon((URL)arg).getImage();
559             } else {
560                 return null;
561             }
562         }
563     }
564 
565     private static URL getThemeDir(String themeName) {
566         return (URL)new Privileged().doPrivileged(Privileged.GET_THEME_DIR, themeName);
567     }
568 
569     private static String getUserTheme() {
570         return (String)new Privileged().doPrivileged(Privileged.GET_USER_THEME, null);
571     }
572 
573     protected void tileImage(Graphics g, Image image, int x0, int y0, int w, int h, float[] alphas) {
574         Graphics2D g2 = (Graphics2D)g;
575         Composite oldComp = g2.getComposite();
576 
577         int sw = image.getWidth(null);
578         int sh = image.getHeight(null);
579         int y = y0;
580         while (y < y0 + h) {
581             sh = Math.min(sh, y0 + h - y);
582             int x = x0;
583             while (x < x0 + w) {
584                 float f = (alphas.length - 1.0F) * x / (x0 + w);
585                 int i = (int)f;
586                 f -= (int)f;
587                 float alpha = (1-f) * alphas[i];
588                 if (i+1 < alphas.length) {
589                     alpha += f * alphas[i+1];
590                 }
591                 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
592                 int swm = Math.min(sw, x0 + w - x);
593                 g.drawImage(image, x, y, x+swm, y+sh, 0, 0, swm, sh, null);
594                 x += swm;
595             }
596             y += sh;
597         }
598         g2.setComposite(oldComp);
599     }
600 
601     private HashMap<String, Image> images = new HashMap<String, Image>();
602 
603     protected Image getImage(String key, Color c) {
604         Image image = images.get(key+"-"+c.getRGB());
605         if (image == null) {
606             image = imageFilter.colorize(getImage(key), c);
607             if (image != null) {
608                 images.put(key+"-"+c.getRGB(), image);
609             }
610         }
611         return image;
612     }
613 
614     protected Image getImage(String key) {
615         Image image = images.get(key);
616         if (image == null) {
617             if (themeDir != null) {
618                 try {
619                     URL url = new URL(themeDir, key);
620                     image = (Image)new Privileged().doPrivileged(Privileged.GET_IMAGE, url);
621                 } catch (MalformedURLException ex) {
622                     //log("Bad image url: "+ themeDir + "/" + key);
623                 }
624             }
625             if (image != null) {
626                 images.put(key, image);
627             }
628         }
629         return image;
630     }
631 
632     private class ColorizeImageFilter extends RGBImageFilter {
633         double cr, cg, cb;
634 
635         public ColorizeImageFilter() {
636             canFilterIndexColorModel = true;
637         }
638 
639         public void setColor(Color color) {
640             cr = color.getRed()   / 255.0;
641             cg = color.getGreen() / 255.0;
642             cb = color.getBlue()  / 255.0;
643         }
644 
645         public Image colorize(Image fromImage, Color c) {
646             setColor(c);
647             ImageProducer producer = new FilteredImageSource(fromImage.getSource(), this);
648             return new ImageIcon(context.getComponent().createImage(producer)).getImage();
649         }
650 
651         public int filterRGB(int x, int y, int rgb) {
652             // Assume all rgb values are shades of gray
653             double grayLevel = 2 * (rgb & 0xff) / 255.0;
654             double r, g, b;
655 
656             if (grayLevel <= 1.0) {
657                 r = cr * grayLevel;
658                 g = cg * grayLevel;
659                 b = cb * grayLevel;
660             } else {
661                 grayLevel -= 1.0;
662                 r = cr + (1.0 - cr) * grayLevel;
663                 g = cg + (1.0 - cg) * grayLevel;
664                 b = cb + (1.0 - cb) * grayLevel;
665             }
666 
667             return ((rgb & 0xff000000) +
668                     (((int)(r * 255)) << 16) +
669                     (((int)(g * 255)) << 8) +
670                     (int)(b * 255));
671         }
672     }
673 
674     protected static JComponent findChild(JComponent parent, String name) {
675         int n = parent.getComponentCount();
676         for (int i = 0; i < n; i++) {
677             JComponent c = (JComponent)parent.getComponent(i);
678             if (name.equals(c.getName())) {
679                 return c;
680             }
681         }
682         return null;
683     }
684 
685 
686     protected class TitlePaneLayout implements LayoutManager {
687         public void addLayoutComponent(String name, Component c) {}
688         public void removeLayoutComponent(Component c) {}
689         public Dimension preferredLayoutSize(Container c)  {
690             return minimumLayoutSize(c);
691         }
692 
693         public Dimension minimumLayoutSize(Container c) {
694             JComponent titlePane = (JComponent)c;
695             Container titlePaneParent = titlePane.getParent();
696             JInternalFrame frame;
697             if (titlePaneParent instanceof JInternalFrame) {
698                 frame = (JInternalFrame)titlePaneParent;
699             } else if (titlePaneParent instanceof JInternalFrame.JDesktopIcon) {
700                 frame = ((JInternalFrame.JDesktopIcon)titlePaneParent).getInternalFrame();
701             } else {
702                 return null;
703             }
704 
705             Dimension buttonDim = calculateButtonSize(titlePane);
706             Insets title_border  = (Insets)getFrameGeometry().get("title_border");
707             Insets button_border = (Insets)getFrameGeometry().get("button_border");
708 
709             // Calculate width.
710             int width = getInt("left_titlebar_edge") + buttonDim.width + getInt("right_titlebar_edge");
711             if (title_border != null) {
712                 width += title_border.left + title_border.right;
713             }
714             if (frame.isClosable()) {
715                 width += buttonDim.width;
716             }
717             if (frame.isMaximizable()) {
718                 width += buttonDim.width;
719             }
720             if (frame.isIconifiable()) {
721                 width += buttonDim.width;
722             }
723             FontMetrics fm = frame.getFontMetrics(titlePane.getFont());
724             String frameTitle = frame.getTitle();
725             int title_w = frameTitle != null ? SwingUtilities2.stringWidth(
726                                frame, fm, frameTitle) : 0;
727             int title_length = frameTitle != null ? frameTitle.length() : 0;
728 
729             // Leave room for three characters in the title.
730             if (title_length > 3) {
731                 int subtitle_w = SwingUtilities2.stringWidth(
732                     frame, fm, frameTitle.substring(0, 3) + "...");
733                 width += (title_w < subtitle_w) ? title_w : subtitle_w;
734             } else {
735                 width += title_w;
736             }
737 
738             // Calculate height.
739             int titleHeight = fm.getHeight() + getInt("title_vertical_pad");
740             if (title_border != null) {
741                 titleHeight += title_border.top + title_border.bottom;
742             }
743             int buttonHeight = buttonDim.height;
744             if (button_border != null) {
745                 buttonHeight += button_border.top + button_border.bottom;
746             }
747             int height = Math.max(buttonHeight, titleHeight);
748 
749             return new Dimension(width, height);
750         }
751 
752         public void layoutContainer(Container c) {
753             JComponent titlePane = (JComponent)c;
754             Container titlePaneParent = titlePane.getParent();
755             JInternalFrame frame;
756             if (titlePaneParent instanceof JInternalFrame) {
757                 frame = (JInternalFrame)titlePaneParent;
758             } else if (titlePaneParent instanceof JInternalFrame.JDesktopIcon) {
759                 frame = ((JInternalFrame.JDesktopIcon)titlePaneParent).getInternalFrame();
760             } else {
761                 return;
762             }
763             Map gm = getFrameGeometry();
764 
765             int w = titlePane.getWidth();
766             int h = titlePane.getHeight();
767 
768             JComponent menuButton     = findChild(titlePane, "InternalFrameTitlePane.menuButton");
769             JComponent minimizeButton = findChild(titlePane, "InternalFrameTitlePane.iconifyButton");
770             JComponent maximizeButton = findChild(titlePane, "InternalFrameTitlePane.maximizeButton");
771             JComponent closeButton    = findChild(titlePane, "InternalFrameTitlePane.closeButton");
772 
773             Insets button_border = (Insets)gm.get("button_border");
774             Dimension buttonDim = calculateButtonSize(titlePane);
775 
776             int y = (button_border != null) ? button_border.top : 0;
777             if (titlePaneParent.getComponentOrientation().isLeftToRight()) {
778                 int x = getInt("left_titlebar_edge");
779 
780                 menuButton.setBounds(x, y, buttonDim.width, buttonDim.height);
781 
782                 x = w - buttonDim.width - getInt("right_titlebar_edge");
783                 if (button_border != null) {
784                     x -= button_border.right;
785                 }
786 
787                 if (frame.isClosable()) {
788                     closeButton.setBounds(x, y, buttonDim.width, buttonDim.height);
789                     x -= buttonDim.width;
790                 }
791 
792                 if (frame.isMaximizable()) {
793                     maximizeButton.setBounds(x, y, buttonDim.width, buttonDim.height);
794                     x -= buttonDim.width;
795                 }
796 
797                 if (frame.isIconifiable()) {
798                     minimizeButton.setBounds(x, y, buttonDim.width, buttonDim.height);
799                 }
800             } else {
801                 int x = w - buttonDim.width - getInt("right_titlebar_edge");
802 
803                 menuButton.setBounds(x, y, buttonDim.width, buttonDim.height);
804 
805                 x = getInt("left_titlebar_edge");
806                 if (button_border != null) {
807                     x += button_border.left;
808                 }
809 
810                 if (frame.isClosable()) {
811                     closeButton.setBounds(x, y, buttonDim.width, buttonDim.height);
812                     x += buttonDim.width;
813                 }
814 
815                 if (frame.isMaximizable()) {
816                     maximizeButton.setBounds(x, y, buttonDim.width, buttonDim.height);
817                     x += buttonDim.width;
818                 }
819 
820                 if (frame.isIconifiable()) {
821                     minimizeButton.setBounds(x, y, buttonDim.width, buttonDim.height);
822                 }
823             }
824         }
825     } // end TitlePaneLayout
826 
827     protected Map getFrameGeometry() {
828         return frameGeometry;
829     }
830 
831     protected void setFrameGeometry(JComponent titlePane, Map gm) {
832         this.frameGeometry = gm;
833         if (getInt("top_height") == 0 && titlePane != null) {
834             gm.put("top_height", Integer.valueOf(titlePane.getHeight()));
835         }
836     }
837 
838     protected int getInt(String key) {
839         Integer i = (Integer)frameGeometry.get(key);
840         if (i == null) {
841             i = variables.get(key);
842         }
843         return (i != null) ? i.intValue() : 0;
844     }
845 
846     protected boolean getBoolean(String key, boolean fallback) {
847         Boolean b = (Boolean)frameGeometry.get(key);
848         return (b != null) ? b.booleanValue() : fallback;
849     }
850 
851 
852     protected void drawArc(Node node, Graphics g) {
853         NamedNodeMap attrs = node.getAttributes();
854         Color color = parseColor(getStringAttr(attrs, "color"));
855         int x = aee.evaluate(getStringAttr(attrs, "x"));
856         int y = aee.evaluate(getStringAttr(attrs, "y"));
857         int w = aee.evaluate(getStringAttr(attrs, "width"));
858         int h = aee.evaluate(getStringAttr(attrs, "height"));
859         int start_angle = aee.evaluate(getStringAttr(attrs, "start_angle"));
860         int extent_angle = aee.evaluate(getStringAttr(attrs, "extent_angle"));
861         boolean filled = getBooleanAttr(node, "filled", false);
862         if (getInt("width") == -1) {
863             x -= w;
864         }
865         if (getInt("height") == -1) {
866             y -= h;
867         }
868         g.setColor(color);
869         if (filled) {
870             g.fillArc(x, y, w, h, start_angle, extent_angle);
871         } else {
872             g.drawArc(x, y, w, h, start_angle, extent_angle);
873         }
874     }
875 
876     protected void drawLine(Node node, Graphics g) {
877         NamedNodeMap attrs = node.getAttributes();
878         Color color = parseColor(getStringAttr(attrs, "color"));
879         int x1 = aee.evaluate(getStringAttr(attrs, "x1"));
880         int y1 = aee.evaluate(getStringAttr(attrs, "y1"));
881         int x2 = aee.evaluate(getStringAttr(attrs, "x2"));
882         int y2 = aee.evaluate(getStringAttr(attrs, "y2"));
883         int lineWidth = aee.evaluate(getStringAttr(attrs, "width"), 1);
884         g.setColor(color);
885         if (lineWidth != 1) {
886             Graphics2D g2d = (Graphics2D)g;
887             Stroke stroke = g2d.getStroke();
888             g2d.setStroke(new BasicStroke((float)lineWidth));
889             g2d.drawLine(x1, y1, x2, y2);
890             g2d.setStroke(stroke);
891         } else {
892             g.drawLine(x1, y1, x2, y2);
893         }
894     }
895 
896     protected void drawRectangle(Node node, Graphics g) {
897         NamedNodeMap attrs = node.getAttributes();
898         Color color = parseColor(getStringAttr(attrs, "color"));
899         boolean filled = getBooleanAttr(node, "filled", false);
900         int x = aee.evaluate(getStringAttr(attrs, "x"));
901         int y = aee.evaluate(getStringAttr(attrs, "y"));
902         int w = aee.evaluate(getStringAttr(attrs, "width"));
903         int h = aee.evaluate(getStringAttr(attrs, "height"));
904         g.setColor(color);
905         if (getInt("width") == -1) {
906             x -= w;
907         }
908         if (getInt("height") == -1) {
909             y -= h;
910         }
911         if (filled) {
912             g.fillRect(x, y, w, h);
913         } else {
914             g.drawRect(x, y, w, h);
915         }
916     }
917 
918     protected void drawTile(Node node, Graphics g, JInternalFrame jif) {
919         NamedNodeMap attrs = node.getAttributes();
920         int x0 = aee.evaluate(getStringAttr(attrs, "x"));
921         int y0 = aee.evaluate(getStringAttr(attrs, "y"));
922         int w = aee.evaluate(getStringAttr(attrs, "width"));
923         int h = aee.evaluate(getStringAttr(attrs, "height"));
924         int tw = aee.evaluate(getStringAttr(attrs, "tile_width"));
925         int th = aee.evaluate(getStringAttr(attrs, "tile_height"));
926         int width  = getInt("width");
927         int height = getInt("height");
928         if (width == -1) {
929             x0 -= w;
930         }
931         if (height == -1) {
932             y0 -= h;
933         }
934         Shape oldClip = g.getClip();
935         if (g instanceof Graphics2D) {
936             ((Graphics2D)g).clip(new Rectangle(x0, y0, w, h));
937         }
938         variables.put("width",  tw);
939         variables.put("height", th);
940 
941         Node draw_ops = getNode("draw_ops", new String[] { "name", getStringAttr(node, "name") });
942 
943         int y = y0;
944         while (y < y0 + h) {
945             int x = x0;
946             while (x < x0 + w) {
947                 g.translate(x, y);
948                 draw(draw_ops, g, jif);
949                 g.translate(-x, -y);
950                 x += tw;
951             }
952             y += th;
953         }
954 
955         variables.put("width",  width);
956         variables.put("height", height);
957         g.setClip(oldClip);
958     }
959 
960     protected void drawTint(Node node, Graphics g) {
961         NamedNodeMap attrs = node.getAttributes();
962         Color color = parseColor(getStringAttr(attrs, "color"));
963         float alpha = Float.parseFloat(getStringAttr(attrs, "alpha"));
964         int x = aee.evaluate(getStringAttr(attrs, "x"));
965         int y = aee.evaluate(getStringAttr(attrs, "y"));
966         int w = aee.evaluate(getStringAttr(attrs, "width"));
967         int h = aee.evaluate(getStringAttr(attrs, "height"));
968         if (getInt("width") == -1) {
969             x -= w;
970         }
971         if (getInt("height") == -1) {
972             y -= h;
973         }
974         if (g instanceof Graphics2D) {
975             Graphics2D g2 = (Graphics2D)g;
976             Composite oldComp = g2.getComposite();
977             AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
978             g2.setComposite(ac);
979             g2.setColor(color);
980             g2.fillRect(x, y, w, h);
981             g2.setComposite(oldComp);
982         }
983     }
984 
985     protected void drawTitle(Node node, Graphics g, JInternalFrame jif) {
986         NamedNodeMap attrs = node.getAttributes();
987         String colorStr = getStringAttr(attrs, "color");
988         int i = colorStr.indexOf("gtk:fg[");
989         if (i > 0) {
990             colorStr = colorStr.substring(0, i) + "gtk:text[" + colorStr.substring(i+7);
991         }
992         Color color = parseColor(colorStr);
993         int x = aee.evaluate(getStringAttr(attrs, "x"));
994         int y = aee.evaluate(getStringAttr(attrs, "y"));
995 
996         String title = jif.getTitle();
997         if (title != null) {
998             FontMetrics fm = SwingUtilities2.getFontMetrics(jif, g);
999             title = SwingUtilities2.clipStringIfNecessary(jif, fm, title,
1000                          calculateTitleArea(jif).width);
1001             g.setColor(color);
1002             SwingUtilities2.drawString(jif, g, title, x, y + fm.getAscent());
1003         }
1004     }
1005 
1006     protected Dimension calculateButtonSize(JComponent titlePane) {
1007         int buttonHeight = getInt("button_height");
1008         if (buttonHeight == 0) {
1009             buttonHeight = titlePane.getHeight();
1010             if (buttonHeight == 0) {
1011                 buttonHeight = 13;
1012             } else {
1013                 Insets button_border = (Insets)frameGeometry.get("button_border");
1014                 if (button_border != null) {
1015                     buttonHeight -= (button_border.top + button_border.bottom);
1016                 }
1017             }
1018         }
1019         int buttonWidth = getInt("button_width");
1020         if (buttonWidth == 0) {
1021             buttonWidth = buttonHeight;
1022             Float aspect_ratio = (Float)frameGeometry.get("aspect_ratio");
1023             if (aspect_ratio != null) {
1024                 buttonWidth = (int)(buttonHeight / aspect_ratio.floatValue());
1025             }
1026         }
1027         return new Dimension(buttonWidth, buttonHeight);
1028     }
1029 
1030     protected Rectangle calculateTitleArea(JInternalFrame jif) {
1031         JComponent titlePane = findChild(jif, "InternalFrame.northPane");
1032         Dimension buttonDim = calculateButtonSize(titlePane);
1033         Insets title_border = (Insets)frameGeometry.get("title_border");
1034         Insets button_border = (Insets)getFrameGeometry().get("button_border");
1035 
1036         Rectangle r = new Rectangle();
1037         r.x = getInt("left_titlebar_edge");
1038         r.y = 0;
1039         r.height = titlePane.getHeight();
1040         if (title_border != null) {
1041             r.x += title_border.left;
1042             r.y += title_border.top;
1043             r.height -= (title_border.top + title_border.bottom);
1044         }
1045 
1046         if (titlePane.getParent().getComponentOrientation().isLeftToRight()) {
1047             r.x += buttonDim.width;
1048             if (button_border != null) {
1049                 r.x += button_border.left;
1050             }
1051             r.width = titlePane.getWidth() - r.x - getInt("right_titlebar_edge");
1052             if (jif.isClosable()) {
1053                 r.width -= buttonDim.width;
1054             }
1055             if (jif.isMaximizable()) {
1056                 r.width -= buttonDim.width;
1057             }
1058             if (jif.isIconifiable()) {
1059                 r.width -= buttonDim.width;
1060             }
1061         } else {
1062             if (jif.isClosable()) {
1063                 r.x += buttonDim.width;
1064             }
1065             if (jif.isMaximizable()) {
1066                 r.x += buttonDim.width;
1067             }
1068             if (jif.isIconifiable()) {
1069                 r.x += buttonDim.width;
1070             }
1071             r.width = titlePane.getWidth() - r.x - getInt("right_titlebar_edge")
1072                     - buttonDim.width;
1073             if (button_border != null) {
1074                 r.x -= button_border.right;
1075             }
1076         }
1077         if (title_border != null) {
1078             r.width -= title_border.right;
1079         }
1080         return r;
1081     }
1082 
1083 
1084     protected int calculateTitleTextWidth(Graphics g, JInternalFrame jif) {
1085         String title = jif.getTitle();
1086         if (title != null) {
1087             Rectangle r = calculateTitleArea(jif);
1088             return Math.min(SwingUtilities2.stringWidth(jif,
1089                      SwingUtilities2.getFontMetrics(jif, g), title), r.width);
1090         }
1091         return 0;
1092     }
1093 
1094     protected void setClip(Node node, Graphics g) {
1095         NamedNodeMap attrs = node.getAttributes();
1096         int x = aee.evaluate(getStringAttr(attrs, "x"));
1097         int y = aee.evaluate(getStringAttr(attrs, "y"));
1098         int w = aee.evaluate(getStringAttr(attrs, "width"));
1099         int h = aee.evaluate(getStringAttr(attrs, "height"));
1100         if (getInt("width") == -1) {
1101             x -= w;
1102         }
1103         if (getInt("height") == -1) {
1104             y -= h;
1105         }
1106         if (g instanceof Graphics2D) {
1107             ((Graphics2D)g).clip(new Rectangle(x, y, w, h));
1108         }
1109     }
1110 
1111     protected void drawGTKArrow(Node node, Graphics g) {
1112         NamedNodeMap attrs = node.getAttributes();
1113         String arrow    = getStringAttr(attrs, "arrow");
1114         String shadow   = getStringAttr(attrs, "shadow");
1115         String stateStr = getStringAttr(attrs, "state").toUpperCase();
1116         int x = aee.evaluate(getStringAttr(attrs, "x"));
1117         int y = aee.evaluate(getStringAttr(attrs, "y"));
1118         int w = aee.evaluate(getStringAttr(attrs, "width"));
1119         int h = aee.evaluate(getStringAttr(attrs, "height"));
1120 
1121         int state = -1;
1122         if ("NORMAL".equals(stateStr)) {
1123             state = ENABLED;
1124         } else if ("SELECTED".equals(stateStr)) {
1125             state = SELECTED;
1126         } else if ("INSENSITIVE".equals(stateStr)) {
1127             state = DISABLED;
1128         } else if ("PRELIGHT".equals(stateStr)) {
1129             state = MOUSE_OVER;
1130         }
1131 
1132         ShadowType shadowType = null;
1133         if ("in".equals(shadow)) {
1134             shadowType = ShadowType.IN;
1135         } else if ("out".equals(shadow)) {
1136             shadowType = ShadowType.OUT;
1137         } else if ("etched_in".equals(shadow)) {
1138             shadowType = ShadowType.ETCHED_IN;
1139         } else if ("etched_out".equals(shadow)) {
1140             shadowType = ShadowType.ETCHED_OUT;
1141         } else if ("none".equals(shadow)) {
1142             shadowType = ShadowType.NONE;
1143         }
1144 
1145         ArrowType direction = null;
1146         if ("up".equals(arrow)) {
1147             direction = ArrowType.UP;
1148         } else if ("down".equals(arrow)) {
1149             direction = ArrowType.DOWN;
1150         } else if ("left".equals(arrow)) {
1151             direction = ArrowType.LEFT;
1152         } else if ("right".equals(arrow)) {
1153             direction = ArrowType.RIGHT;
1154         }
1155 
1156         GTKPainter.INSTANCE.paintMetacityElement(context, g, state,
1157                 "metacity-arrow", x, y, w, h, shadowType, direction);
1158     }
1159 
1160     protected void drawGTKBox(Node node, Graphics g) {
1161         NamedNodeMap attrs = node.getAttributes();
1162         String shadow   = getStringAttr(attrs, "shadow");
1163         String stateStr = getStringAttr(attrs, "state").toUpperCase();
1164         int x = aee.evaluate(getStringAttr(attrs, "x"));
1165         int y = aee.evaluate(getStringAttr(attrs, "y"));
1166         int w = aee.evaluate(getStringAttr(attrs, "width"));
1167         int h = aee.evaluate(getStringAttr(attrs, "height"));
1168 
1169         int state = -1;
1170         if ("NORMAL".equals(stateStr)) {
1171             state = ENABLED;
1172         } else if ("SELECTED".equals(stateStr)) {
1173             state = SELECTED;
1174         } else if ("INSENSITIVE".equals(stateStr)) {
1175             state = DISABLED;
1176         } else if ("PRELIGHT".equals(stateStr)) {
1177             state = MOUSE_OVER;
1178         }
1179 
1180         ShadowType shadowType = null;
1181         if ("in".equals(shadow)) {
1182             shadowType = ShadowType.IN;
1183         } else if ("out".equals(shadow)) {
1184             shadowType = ShadowType.OUT;
1185         } else if ("etched_in".equals(shadow)) {
1186             shadowType = ShadowType.ETCHED_IN;
1187         } else if ("etched_out".equals(shadow)) {
1188             shadowType = ShadowType.ETCHED_OUT;
1189         } else if ("none".equals(shadow)) {
1190             shadowType = ShadowType.NONE;
1191         }
1192         GTKPainter.INSTANCE.paintMetacityElement(context, g, state,
1193                 "metacity-box", x, y, w, h, shadowType, null);
1194     }
1195 
1196     protected void drawGTKVLine(Node node, Graphics g) {
1197         NamedNodeMap attrs = node.getAttributes();
1198         String stateStr = getStringAttr(attrs, "state").toUpperCase();
1199 
1200         int x  = aee.evaluate(getStringAttr(attrs, "x"));
1201         int y1 = aee.evaluate(getStringAttr(attrs, "y1"));
1202         int y2 = aee.evaluate(getStringAttr(attrs, "y2"));
1203 
1204         int state = -1;
1205         if ("NORMAL".equals(stateStr)) {
1206             state = ENABLED;
1207         } else if ("SELECTED".equals(stateStr)) {
1208             state = SELECTED;
1209         } else if ("INSENSITIVE".equals(stateStr)) {
1210             state = DISABLED;
1211         } else if ("PRELIGHT".equals(stateStr)) {
1212             state = MOUSE_OVER;
1213         }
1214 
1215         GTKPainter.INSTANCE.paintMetacityElement(context, g, state,
1216                 "metacity-vline", x, y1, 1, y2 - y1, null, null);
1217     }
1218 
1219     protected void drawGradient(Node node, Graphics g) {
1220         NamedNodeMap attrs = node.getAttributes();
1221         String type = getStringAttr(attrs, "type");
1222         float alpha = getFloatAttr(node, "alpha", -1F);
1223         int x = aee.evaluate(getStringAttr(attrs, "x"));
1224         int y = aee.evaluate(getStringAttr(attrs, "y"));
1225         int w = aee.evaluate(getStringAttr(attrs, "width"));
1226         int h = aee.evaluate(getStringAttr(attrs, "height"));
1227         if (getInt("width") == -1) {
1228             x -= w;
1229         }
1230         if (getInt("height") == -1) {
1231             y -= h;
1232         }
1233 
1234         // Get colors from child nodes
1235         Node[] colorNodes = getNodesByName(node, "color");
1236         Color[] colors = new Color[colorNodes.length];
1237         for (int i = 0; i < colorNodes.length; i++) {
1238             colors[i] = parseColor(getStringAttr(colorNodes[i], "value"));
1239         }
1240 
1241         boolean horizontal = ("diagonal".equals(type) || "horizontal".equals(type));
1242         boolean vertical   = ("diagonal".equals(type) || "vertical".equals(type));
1243 
1244         if (g instanceof Graphics2D) {
1245             Graphics2D g2 = (Graphics2D)g;
1246             Composite oldComp = g2.getComposite();
1247             if (alpha >= 0F) {
1248                 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
1249             }
1250             int n = colors.length - 1;
1251             for (int i = 0; i < n; i++) {
1252                 g2.setPaint(new GradientPaint(x + (horizontal ? (i*w/n) : 0),
1253                                               y + (vertical   ? (i*h/n) : 0),
1254                                               colors[i],
1255                                               x + (horizontal ? ((i+1)*w/n) : 0),
1256                                               y + (vertical   ? ((i+1)*h/n) : 0),
1257                                               colors[i+1]));
1258                 g2.fillRect(x + (horizontal ? (i*w/n) : 0),
1259                             y + (vertical   ? (i*h/n) : 0),
1260                             (horizontal ? (w/n) : w),
1261                             (vertical   ? (h/n) : h));
1262             }
1263             g2.setComposite(oldComp);
1264         }
1265     }
1266 
1267     protected void drawImage(Node node, Graphics g) {
1268         NamedNodeMap attrs = node.getAttributes();
1269         String filename = getStringAttr(attrs, "filename");
1270         String colorizeStr = getStringAttr(attrs, "colorize");
1271         Color colorize = (colorizeStr != null) ? parseColor(colorizeStr) : null;
1272         String alpha = getStringAttr(attrs, "alpha");
1273         Image object = (colorize != null) ? getImage(filename, colorize) : getImage(filename);
1274         variables.put("object_width",  object.getWidth(null));
1275         variables.put("object_height", object.getHeight(null));
1276         String fill_type = getStringAttr(attrs, "fill_type");
1277         int x = aee.evaluate(getStringAttr(attrs, "x"));
1278         int y = aee.evaluate(getStringAttr(attrs, "y"));
1279         int w = aee.evaluate(getStringAttr(attrs, "width"));
1280         int h = aee.evaluate(getStringAttr(attrs, "height"));
1281         if (getInt("width") == -1) {
1282             x -= w;
1283         }
1284         if (getInt("height") == -1) {
1285             y -= h;
1286         }
1287 
1288         if (alpha != null) {
1289             if ("tile".equals(fill_type)) {
1290                 StringTokenizer tokenizer = new StringTokenizer(alpha, ":");
1291                 float[] alphas = new float[tokenizer.countTokens()];
1292                 for (int i = 0; i < alphas.length; i++) {
1293                     alphas[i] = Float.parseFloat(tokenizer.nextToken());
1294                 }
1295                 tileImage(g, object, x, y, w, h, alphas);
1296             } else {
1297                 float a = Float.parseFloat(alpha);
1298                 if (g instanceof Graphics2D) {
1299                     Graphics2D g2 = (Graphics2D)g;
1300                     Composite oldComp = g2.getComposite();
1301                     g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, a));
1302                     g2.drawImage(object, x, y, w, h, null);
1303                     g2.setComposite(oldComp);
1304                 }
1305             }
1306         } else {
1307             g.drawImage(object, x, y, w, h, null);
1308         }
1309     }
1310 
1311     protected void drawIcon(Node node, Graphics g, JInternalFrame jif) {
1312         Icon icon = jif.getFrameIcon();
1313         if (icon == null) {
1314             return;
1315         }
1316 
1317         NamedNodeMap attrs = node.getAttributes();
1318         String alpha = getStringAttr(attrs, "alpha");
1319         int x = aee.evaluate(getStringAttr(attrs, "x"));
1320         int y = aee.evaluate(getStringAttr(attrs, "y"));
1321         int w = aee.evaluate(getStringAttr(attrs, "width"));
1322         int h = aee.evaluate(getStringAttr(attrs, "height"));
1323         if (getInt("width") == -1) {
1324             x -= w;
1325         }
1326         if (getInt("height") == -1) {
1327             y -= h;
1328         }
1329 
1330         if (alpha != null) {
1331             float a = Float.parseFloat(alpha);
1332             if (g instanceof Graphics2D) {
1333                 Graphics2D g2 = (Graphics2D)g;
1334                 Composite oldComp = g2.getComposite();
1335                 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, a));
1336                 icon.paintIcon(jif, g, x, y);
1337                 g2.setComposite(oldComp);
1338             }
1339         } else {
1340             icon.paintIcon(jif, g, x, y);
1341         }
1342     }
1343 
1344     protected void drawInclude(Node node, Graphics g, JInternalFrame jif) {
1345         int oldWidth  = getInt("width");
1346         int oldHeight = getInt("height");
1347 
1348         NamedNodeMap attrs = node.getAttributes();
1349         int x = aee.evaluate(getStringAttr(attrs, "x"),       0);
1350         int y = aee.evaluate(getStringAttr(attrs, "y"),       0);
1351         int w = aee.evaluate(getStringAttr(attrs, "width"),  -1);
1352         int h = aee.evaluate(getStringAttr(attrs, "height"), -1);
1353 
1354         if (w != -1) {
1355             variables.put("width",  w);
1356         }
1357         if (h != -1) {
1358             variables.put("height", h);
1359         }
1360 
1361         Node draw_ops = getNode("draw_ops", new String[] {
1362             "name", getStringAttr(node, "name")
1363         });
1364         g.translate(x, y);
1365         draw(draw_ops, g, jif);
1366         g.translate(-x, -y);
1367 
1368         if (w != -1) {
1369             variables.put("width",  oldWidth);
1370         }
1371         if (h != -1) {
1372             variables.put("height", oldHeight);
1373         }
1374     }
1375 
1376     protected void draw(Node draw_ops, Graphics g, JInternalFrame jif) {
1377         if (draw_ops != null) {
1378             NodeList nodes = draw_ops.getChildNodes();
1379             if (nodes != null) {
1380                 Shape oldClip = g.getClip();
1381                 for (int i = 0; i < nodes.getLength(); i++) {
1382                     Node child = nodes.item(i);
1383                     if (child.getNodeType() == Node.ELEMENT_NODE) {
1384                         try {
1385                             String name = child.getNodeName();
1386                             if ("include".equals(name)) {
1387                                 drawInclude(child, g, jif);
1388                             } else if ("arc".equals(name)) {
1389                                 drawArc(child, g);
1390                             } else if ("clip".equals(name)) {
1391                                 setClip(child, g);
1392                             } else if ("gradient".equals(name)) {
1393                                 drawGradient(child, g);
1394                             } else if ("gtk_arrow".equals(name)) {
1395                                 drawGTKArrow(child, g);
1396                             } else if ("gtk_box".equals(name)) {
1397                                 drawGTKBox(child, g);
1398                             } else if ("gtk_vline".equals(name)) {
1399                                 drawGTKVLine(child, g);
1400                             } else if ("image".equals(name)) {
1401                                 drawImage(child, g);
1402                             } else if ("icon".equals(name)) {
1403                                 drawIcon(child, g, jif);
1404                             } else if ("line".equals(name)) {
1405                                 drawLine(child, g);
1406                             } else if ("rectangle".equals(name)) {
1407                                 drawRectangle(child, g);
1408                             } else if ("tint".equals(name)) {
1409                                 drawTint(child, g);
1410                             } else if ("tile".equals(name)) {
1411                                 drawTile(child, g, jif);
1412                             } else if ("title".equals(name)) {
1413                                 drawTitle(child, g, jif);
1414                             } else {
1415                                 System.err.println("Unknown Metacity drawing op: "+child);
1416                             }
1417                         } catch (NumberFormatException ex) {
1418                             logError(themeName, ex);
1419                         }
1420                     }
1421                 }
1422                 g.setClip(oldClip);
1423             }
1424         }
1425     }
1426 
1427     protected void drawPiece(Node frame_style, Graphics g, String position, int x, int y,
1428                              int width, int height, JInternalFrame jif) {
1429         Node piece = getNode(frame_style, "piece", new String[] { "position", position });
1430         if (piece != null) {
1431             Node draw_ops;
1432             String draw_ops_name = getStringAttr(piece, "draw_ops");
1433             if (draw_ops_name != null) {
1434                 draw_ops = getNode("draw_ops", new String[] { "name", draw_ops_name });
1435             } else {
1436                 draw_ops = getNode(piece, "draw_ops", null);
1437             }
1438             variables.put("width",  width);
1439             variables.put("height", height);
1440             g.translate(x, y);
1441             draw(draw_ops, g, jif);
1442             g.translate(-x, -y);
1443         }
1444     }
1445 
1446 
1447     Insets getBorderInsets(SynthContext context, Insets insets) {
1448         updateFrameGeometry(context);
1449 
1450         if (insets == null) {
1451             insets = new Insets(0, 0, 0, 0);
1452         }
1453         insets.top    = ((Insets)frameGeometry.get("title_border")).top;
1454         insets.bottom = getInt("bottom_height");
1455         insets.left   = getInt("left_width");
1456         insets.right  = getInt("right_width");
1457         return insets;
1458     }
1459 
1460 
1461     private void updateFrameGeometry(SynthContext context) {
1462         this.context = context;
1463         JComponent comp = context.getComponent();
1464         JComponent titlePane = findChild(comp, "InternalFrame.northPane");
1465 
1466         JInternalFrame jif = null;
1467         if (comp instanceof JInternalFrame) {
1468             jif = (JInternalFrame)comp;
1469         } else if (comp instanceof JInternalFrame.JDesktopIcon) {
1470             jif = ((JInternalFrame.JDesktopIcon)comp).getInternalFrame();
1471         } else {
1472             assert false : "component is not JInternalFrame or JInternalFrame.JDesktopIcon";
1473             return;
1474         }
1475 
1476         if (frame_style_set == null) {
1477             Node window = getNode("window", new String[]{"type", "normal"});
1478 
1479             if (window != null) {
1480                 frame_style_set = getNode("frame_style_set",
1481                         new String[] {"name", getStringAttr(window, "style_set")});
1482             }
1483 
1484             if (frame_style_set == null) {
1485                 frame_style_set = getNode("frame_style_set", new String[] {"name", "normal"});
1486             }
1487         }
1488 
1489         if (frame_style_set != null) {
1490             Node frame = getNode(frame_style_set, "frame", new String[] {
1491                 "focus", (jif.isSelected() ? "yes" : "no"),
1492                 "state", (jif.isMaximum() ? "maximized" : "normal")
1493             });
1494 
1495             if (frame != null) {
1496                 Node frame_style = getNode("frame_style", new String[] {
1497                     "name", getStringAttr(frame, "style")
1498                 });
1499                 if (frame_style != null) {
1500                     Map gm = frameGeometries.get(getStringAttr(frame_style, "geometry"));
1501 
1502                     setFrameGeometry(titlePane, gm);
1503                 }
1504             }
1505         }
1506     }
1507 
1508 
1509     protected static void logError(String themeName, Exception ex) {
1510         logError(themeName, ex.toString());
1511     }
1512 
1513     protected static void logError(String themeName, String msg) {
1514         if (!errorLogged) {
1515             System.err.println("Exception in Metacity for theme \""+themeName+"\": "+msg);
1516             errorLogged = true;
1517         }
1518     }
1519 
1520 
1521     // XML Parsing
1522 
1523 
1524     protected static Document getXMLDoc(final URL xmlFile)
1525                                 throws IOException,
1526                                        ParserConfigurationException,
1527                                        SAXException {
1528         if (documentBuilder == null) {
1529             documentBuilder =
1530                 DocumentBuilderFactory.newInstance().newDocumentBuilder();
1531         }
1532         InputStream inputStream =
1533             AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
1534                 public InputStream run() {
1535                     try {
1536                         return new BufferedInputStream(xmlFile.openStream());
1537                     } catch (IOException ex) {
1538                         return null;
1539                     }
1540                 }
1541             });
1542 
1543         Document doc = null;
1544         if (inputStream != null) {
1545             doc = documentBuilder.parse(inputStream);
1546         }
1547         return doc;
1548     }
1549 
1550 
1551     protected Node[] getNodesByName(Node parent, String name) {
1552         NodeList nodes = parent.getChildNodes(); // ElementNode
1553         int n = nodes.getLength();
1554         ArrayList<Node> list = new ArrayList<Node>();
1555         for (int i=0; i < n; i++) {
1556             Node node = nodes.item(i);
1557             if (name.equals(node.getNodeName())) {
1558                 list.add(node);
1559             }
1560         }
1561         return list.toArray(new Node[list.size()]);
1562     }
1563 
1564 
1565 
1566     protected Node getNode(String tagName, String[] attrs) {
1567         NodeList nodes = xmlDoc.getElementsByTagName(tagName);
1568         return (nodes != null) ? getNode(nodes, tagName, attrs) : null;
1569     }
1570 
1571     protected Node getNode(Node parent, String name, String[] attrs) {
1572         Node node = null;
1573         NodeList nodes = parent.getChildNodes();
1574         if (nodes != null) {
1575             node = getNode(nodes, name, attrs);
1576         }
1577         if (node == null) {
1578             String inheritFrom = getStringAttr(parent, "parent");
1579             if (inheritFrom != null) {
1580                 Node inheritFromNode = getNode(parent.getParentNode(),
1581                                                parent.getNodeName(),
1582                                                new String[] { "name", inheritFrom });
1583                 if (inheritFromNode != null) {
1584                     node = getNode(inheritFromNode, name, attrs);
1585                 }
1586             }
1587         }
1588         return node;
1589     }
1590 
1591     protected Node getNode(NodeList nodes, String name, String[] attrs) {
1592         int n = nodes.getLength();
1593         for (int i=0; i < n; i++) {
1594             Node node = nodes.item(i);
1595             if (name.equals(node.getNodeName())) {
1596                 if (attrs != null) {
1597                     NamedNodeMap nodeAttrs = node.getAttributes();
1598                     if (nodeAttrs != null) {
1599                         boolean matches = true;
1600                         int nAttrs = attrs.length / 2;
1601                         for (int a = 0; a < nAttrs; a++) {
1602                             String aName  = attrs[a * 2];
1603                             String aValue = attrs[a * 2 + 1];
1604                             Node attr = nodeAttrs.getNamedItem(aName);
1605                             if (attr == null ||
1606                                 aValue != null && !aValue.equals(attr.getNodeValue())) {
1607                                 matches = false;
1608                                 break;
1609                             }
1610                         }
1611                         if (matches) {
1612                             return node;
1613                         }
1614                     }
1615                 } else {
1616                     return node;
1617                 }
1618             }
1619         }
1620         return null;
1621     }
1622 
1623     protected String getStringAttr(Node node, String name) {
1624         String value = null;
1625         NamedNodeMap attrs = node.getAttributes();
1626         if (attrs != null) {
1627             value = getStringAttr(attrs, name);
1628             if (value == null) {
1629                 String inheritFrom = getStringAttr(attrs, "parent");
1630                 if (inheritFrom != null) {
1631                     Node inheritFromNode = getNode(node.getParentNode(),
1632                                                    node.getNodeName(),
1633                                                    new String[] { "name", inheritFrom });
1634                     if (inheritFromNode != null) {
1635                         value = getStringAttr(inheritFromNode, name);
1636                     }
1637                 }
1638             }
1639         }
1640         return value;
1641     }
1642 
1643     protected String getStringAttr(NamedNodeMap attrs, String name) {
1644         Node item = attrs.getNamedItem(name);
1645         return (item != null) ? item.getNodeValue() : null;
1646     }
1647 
1648     protected boolean getBooleanAttr(Node node, String name, boolean fallback) {
1649         String str = getStringAttr(node, name);
1650         if (str != null) {
1651             return Boolean.valueOf(str).booleanValue();
1652         }
1653         return fallback;
1654     }
1655 
1656     protected int getIntAttr(Node node, String name, int fallback) {
1657         String str = getStringAttr(node, name);
1658         int value = fallback;
1659         if (str != null) {
1660             try {
1661                 value = Integer.parseInt(str);
1662             } catch (NumberFormatException ex) {
1663                 logError(themeName, ex);
1664             }
1665         }
1666         return value;
1667     }
1668 
1669     protected float getFloatAttr(Node node, String name, float fallback) {
1670         String str = getStringAttr(node, name);
1671         float value = fallback;
1672         if (str != null) {
1673             try {
1674                 value = Float.parseFloat(str);
1675             } catch (NumberFormatException ex) {
1676                 logError(themeName, ex);
1677             }
1678         }
1679         return value;
1680     }
1681 
1682 
1683 
1684     protected Color parseColor(String str) {
1685         StringTokenizer tokenizer = new StringTokenizer(str, "/");
1686         int n = tokenizer.countTokens();
1687         if (n > 1) {
1688             String function = tokenizer.nextToken();
1689             if ("shade".equals(function)) {
1690                 assert (n == 3);
1691                 Color c = parseColor2(tokenizer.nextToken());
1692                 float alpha = Float.parseFloat(tokenizer.nextToken());
1693                 return GTKColorType.adjustColor(c, 1.0F, alpha, alpha);
1694             } else if ("blend".equals(function)) {
1695                 assert (n == 4);
1696                 Color  bg = parseColor2(tokenizer.nextToken());
1697                 Color  fg = parseColor2(tokenizer.nextToken());
1698                 float alpha = Float.parseFloat(tokenizer.nextToken());
1699                 if (alpha > 1.0f) {
1700                     alpha = 1.0f / alpha;
1701                 }
1702 
1703                 return new Color((int)(bg.getRed() + ((fg.getRed() - bg.getRed()) * alpha)),
1704                                  (int)(bg.getRed() + ((fg.getRed() - bg.getRed()) * alpha)),
1705                                  (int)(bg.getRed() + ((fg.getRed() - bg.getRed()) * alpha)));
1706             } else {
1707                 System.err.println("Unknown Metacity color function="+str);
1708                 return null;
1709             }
1710         } else {
1711             return parseColor2(str);
1712         }
1713     }
1714 
1715     protected Color parseColor2(String str) {
1716         Color c = null;
1717         if (str.startsWith("gtk:")) {
1718             int i1 = str.indexOf('[');
1719             if (i1 > 3) {
1720                 String typeStr = str.substring(4, i1).toLowerCase();
1721                 int i2 = str.indexOf(']');
1722                 if (i2 > i1+1) {
1723                     String stateStr = str.substring(i1+1, i2).toUpperCase();
1724                     int state = -1;
1725                     if ("ACTIVE".equals(stateStr)) {
1726                         state = PRESSED;
1727                     } else if ("INSENSITIVE".equals(stateStr)) {
1728                         state = DISABLED;
1729                     } else if ("NORMAL".equals(stateStr)) {
1730                         state = ENABLED;
1731                     } else if ("PRELIGHT".equals(stateStr)) {
1732                         state = MOUSE_OVER;
1733                     } else if ("SELECTED".equals(stateStr)) {
1734                         state = SELECTED;
1735                     }
1736                     ColorType type = null;
1737                     if ("fg".equals(typeStr)) {
1738                         type = GTKColorType.FOREGROUND;
1739                     } else if ("bg".equals(typeStr)) {
1740                         type = GTKColorType.BACKGROUND;
1741                     } else if ("base".equals(typeStr)) {
1742                         type = GTKColorType.TEXT_BACKGROUND;
1743                     } else if ("text".equals(typeStr)) {
1744                         type = GTKColorType.TEXT_FOREGROUND;
1745                     } else if ("dark".equals(typeStr)) {
1746                         type = GTKColorType.DARK;
1747                     } else if ("light".equals(typeStr)) {
1748                         type = GTKColorType.LIGHT;
1749                     }
1750                     if (state >= 0 && type != null) {
1751                         c = ((GTKStyle)context.getStyle()).getGTKColor(context, state, type);
1752                     }
1753                 }
1754             }
1755         }
1756         if (c == null) {
1757             c = parseColorString(str);
1758         }
1759         return c;
1760     }
1761 
1762     private static Color parseColorString(String str) {
1763         if (str.charAt(0) == '#') {
1764             str = str.substring(1);
1765 
1766             int i = str.length();
1767 
1768             if (i < 3 || i > 12 || (i % 3) != 0) {
1769                 return null;
1770             }
1771 
1772             i /= 3;
1773 
1774             int r;
1775             int g;
1776             int b;
1777 
1778             try {
1779                 r = Integer.parseInt(str.substring(0, i), 16);
1780                 g = Integer.parseInt(str.substring(i, i * 2), 16);
1781                 b = Integer.parseInt(str.substring(i * 2, i * 3), 16);
1782             } catch (NumberFormatException nfe) {
1783                 return null;
1784             }
1785 
1786             if (i == 4) {
1787                 return new ColorUIResource(r / 65535.0f, g / 65535.0f, b / 65535.0f);
1788             } else if (i == 1) {
1789                 return new ColorUIResource(r / 15.0f, g / 15.0f, b / 15.0f);
1790             } else if (i == 2) {
1791                 return new ColorUIResource(r, g, b);
1792             } else {
1793                 return new ColorUIResource(r / 4095.0f, g / 4095.0f, b / 4095.0f);
1794             }
1795         } else {
1796             return XColors.lookupColor(str);
1797         }
1798     }
1799 
1800     class ArithmeticExpressionEvaluator {
1801         private PeekableStringTokenizer tokenizer;
1802 
1803         int evaluate(String expr) {
1804             tokenizer = new PeekableStringTokenizer(expr, " \t+-*/%()", true);
1805             return Math.round(expression());
1806         }
1807 
1808         int evaluate(String expr, int fallback) {
1809             return (expr != null) ? evaluate(expr) : fallback;
1810         }
1811 
1812         public float expression() {
1813             float value = getTermValue();
1814             boolean done = false;
1815             while (!done && tokenizer.hasMoreTokens()) {
1816                 String next = tokenizer.peek();
1817                 if ("+".equals(next) ||
1818                     "-".equals(next) ||
1819                     "`max`".equals(next) ||
1820                     "`min`".equals(next)) {
1821                     tokenizer.nextToken();
1822                     float value2 = getTermValue();
1823                     if ("+".equals(next)) {
1824                         value += value2;
1825                     } else if ("-".equals(next)) {
1826                         value -= value2;
1827                     } else if ("`max`".equals(next)) {
1828                         value = Math.max(value, value2);
1829                     } else if ("`min`".equals(next)) {
1830                         value = Math.min(value, value2);
1831                     }
1832                 } else {
1833                     done = true;
1834                 }
1835             }
1836             return value;
1837         }
1838 
1839         public float getTermValue() {
1840             float value = getFactorValue();
1841             boolean done = false;
1842             while (!done && tokenizer.hasMoreTokens()) {
1843                 String next = tokenizer.peek();
1844                 if ("*".equals(next) || "/".equals(next) || "%".equals(next)) {
1845                     tokenizer.nextToken();
1846                     float value2 = getFactorValue();
1847                     if ("*".equals(next)) {
1848                         value *= value2;
1849                     } else if ("/".equals(next)) {
1850                         value /= value2;
1851                     } else {
1852                         value %= value2;
1853                     }
1854                 } else {
1855                     done = true;
1856                 }
1857             }
1858             return value;
1859         }
1860 
1861         public float getFactorValue() {
1862             float value;
1863             if ("(".equals(tokenizer.peek())) {
1864                 tokenizer.nextToken();
1865                 value = expression();
1866                 tokenizer.nextToken(); // skip right paren
1867             } else {
1868                 String token = tokenizer.nextToken();
1869                 if (Character.isDigit(token.charAt(0))) {
1870                     value = Float.parseFloat(token);
1871                 } else {
1872                     Integer i = variables.get(token);
1873                     if (i == null) {
1874                         i = (Integer)getFrameGeometry().get(token);
1875                     }
1876                     if (i == null) {
1877                         logError(themeName, "Variable \"" + token + "\" not defined");
1878                         return 0;
1879                     }
1880                     value = (i != null) ? i.intValue() : 0F;
1881                 }
1882             }
1883             return value;
1884         }
1885 
1886 
1887     }
1888 
1889     static class PeekableStringTokenizer extends StringTokenizer {
1890         String token = null;
1891 
1892         public PeekableStringTokenizer(String str, String delim,
1893                                        boolean returnDelims) {
1894             super(str, delim, returnDelims);
1895             peek();
1896         }
1897 
1898         public String peek() {
1899             if (token == null) {
1900                 token = nextToken();
1901             }
1902             return token;
1903         }
1904 
1905         public boolean hasMoreTokens() {
1906             return (token != null || super.hasMoreTokens());
1907         }
1908 
1909         public String nextToken() {
1910             if (token != null) {
1911                 String t = token;
1912                 token = null;
1913                 if (hasMoreTokens()) {
1914                     peek();
1915                 }
1916                 return t;
1917             } else {
1918                 String token = super.nextToken();
1919                 while ((token.equals(" ") || token.equals("\t"))
1920                        && hasMoreTokens()) {
1921                     token = super.nextToken();
1922                 }
1923                 return token;
1924             }
1925         }
1926     }
1927 
1928 
1929     static class RoundRectClipShape extends RectangularShape {
1930         static final int TOP_LEFT = 1;
1931         static final int TOP_RIGHT = 2;
1932         static final int BOTTOM_LEFT = 4;
1933         static final int BOTTOM_RIGHT = 8;
1934 
1935         int x;
1936         int y;
1937         int width;
1938         int height;
1939         int arcwidth;
1940         int archeight;
1941         int corners;
1942 
1943         public RoundRectClipShape() {
1944         }
1945 
1946         public RoundRectClipShape(int x, int y, int w, int h,
1947                                   int arcw, int arch, int corners) {
1948             setRoundedRect(x, y, w, h, arcw, arch, corners);
1949         }
1950 
1951         public void setRoundedRect(int x, int y, int w, int h,
1952                                    int arcw, int arch, int corners) {
1953             this.corners = corners;
1954             this.x = x;
1955             this.y = y;
1956             this.width = w;
1957             this.height = h;
1958             this.arcwidth = arcw;
1959             this.archeight = arch;
1960         }
1961 
1962         public double getX() {
1963             return (double)x;
1964         }
1965 
1966         public double getY() {
1967             return (double)y;
1968         }
1969 
1970         public double getWidth() {
1971             return (double)width;
1972         }
1973 
1974         public double getHeight() {
1975             return (double)height;
1976         }
1977 
1978         public double getArcWidth() {
1979             return (double)arcwidth;
1980         }
1981 
1982         public double getArcHeight() {
1983             return (double)archeight;
1984         }
1985 
1986         public boolean isEmpty() {
1987             return false;  // Not called
1988         }
1989 
1990         public Rectangle2D getBounds2D() {
1991             return null;  // Not called
1992         }
1993 
1994         public int getCornerFlags() {
1995             return corners;
1996         }
1997 
1998         public void setFrame(double x, double y, double w, double h) {
1999             // Not called
2000         }
2001 
2002         public boolean contains(double x, double y) {
2003             return false;  // Not called
2004         }
2005 
2006         private int classify(double coord, double left, double right, double arcsize) {
2007             return 0;  // Not called
2008         }
2009 
2010         public boolean intersects(double x, double y, double w, double h) {
2011             return false;  // Not called
2012         }
2013 
2014         public boolean contains(double x, double y, double w, double h) {
2015             return false;  // Not called
2016         }
2017 
2018         public PathIterator getPathIterator(AffineTransform at) {
2019             return new RoundishRectIterator(this, at);
2020         }
2021 
2022 
2023         static class RoundishRectIterator implements PathIterator {
2024             double x, y, w, h, aw, ah;
2025             AffineTransform affine;
2026             int index;
2027 
2028             double ctrlpts[][];
2029             int types[];
2030 
2031             private static final double angle = Math.PI / 4.0;
2032             private static final double a = 1.0 - Math.cos(angle);
2033             private static final double b = Math.tan(angle);
2034             private static final double c = Math.sqrt(1.0 + b * b) - 1 + a;
2035             private static final double cv = 4.0 / 3.0 * a * b / c;
2036             private static final double acv = (1.0 - cv) / 2.0;
2037 
2038             // For each array:
2039             //     4 values for each point {v0, v1, v2, v3}:
2040             //         point = (x + v0 * w + v1 * arcWidth,
2041             //                  y + v2 * h + v3 * arcHeight);
2042             private static final double CtrlPtTemplate[][] = {
2043                 {  0.0,  0.0,  1.0,  0.0 },     /* BOTTOM LEFT corner */
2044                 {  0.0,  0.0,  1.0, -0.5 },     /* BOTTOM LEFT arc start */
2045                 {  0.0,  0.0,  1.0, -acv,       /* BOTTOM LEFT arc curve */
2046                    0.0,  acv,  1.0,  0.0,
2047                    0.0,  0.5,  1.0,  0.0 },
2048                 {  1.0,  0.0,  1.0,  0.0 },     /* BOTTOM RIGHT corner */
2049                 {  1.0, -0.5,  1.0,  0.0 },     /* BOTTOM RIGHT arc start */
2050                 {  1.0, -acv,  1.0,  0.0,       /* BOTTOM RIGHT arc curve */
2051                    1.0,  0.0,  1.0, -acv,
2052                    1.0,  0.0,  1.0, -0.5 },
2053                 {  1.0,  0.0,  0.0,  0.0 },     /* TOP RIGHT corner */
2054                 {  1.0,  0.0,  0.0,  0.5 },     /* TOP RIGHT arc start */
2055                 {  1.0,  0.0,  0.0,  acv,       /* TOP RIGHT arc curve */
2056                    1.0, -acv,  0.0,  0.0,
2057                    1.0, -0.5,  0.0,  0.0 },
2058                 {  0.0,  0.0,  0.0,  0.0 },     /* TOP LEFT corner */
2059                 {  0.0,  0.5,  0.0,  0.0 },     /* TOP LEFT arc start */
2060                 {  0.0,  acv,  0.0,  0.0,       /* TOP LEFT arc curve */
2061                    0.0,  0.0,  0.0,  acv,
2062                    0.0,  0.0,  0.0,  0.5 },
2063                 {},                             /* Closing path element */
2064             };
2065             private static final int CornerFlags[] = {
2066                 RoundRectClipShape.BOTTOM_LEFT,
2067                 RoundRectClipShape.BOTTOM_RIGHT,
2068                 RoundRectClipShape.TOP_RIGHT,
2069                 RoundRectClipShape.TOP_LEFT,
2070             };
2071 
2072             RoundishRectIterator(RoundRectClipShape rr, AffineTransform at) {
2073                 this.x = rr.getX();
2074                 this.y = rr.getY();
2075                 this.w = rr.getWidth();
2076                 this.h = rr.getHeight();
2077                 this.aw = Math.min(w, Math.abs(rr.getArcWidth()));
2078                 this.ah = Math.min(h, Math.abs(rr.getArcHeight()));
2079                 this.affine = at;
2080                 if (w < 0 || h < 0) {
2081                     // Don't draw anything...
2082                     ctrlpts = new double[0][];
2083                     types = new int[0];
2084                 } else {
2085                     int corners = rr.getCornerFlags();
2086                     int numedges = 5;  // 4xCORNER_POINT, CLOSE
2087                     for (int i = 1; i < 0x10; i <<= 1) {
2088                         // Add one for each corner that has a curve
2089                         if ((corners & i) != 0) numedges++;
2090                     }
2091                     ctrlpts = new double[numedges][];
2092                     types = new int[numedges];
2093                     int j = 0;
2094                     for (int i = 0; i < 4; i++) {
2095                         types[j] = SEG_LINETO;
2096                         if ((corners & CornerFlags[i]) == 0) {
2097                             ctrlpts[j++] = CtrlPtTemplate[i*3+0];
2098                         } else {
2099                             ctrlpts[j++] = CtrlPtTemplate[i*3+1];
2100                             types[j] = SEG_CUBICTO;
2101                             ctrlpts[j++] = CtrlPtTemplate[i*3+2];
2102                         }
2103                     }
2104                     types[j] = SEG_CLOSE;
2105                     ctrlpts[j++] = CtrlPtTemplate[12];
2106                     types[0] = SEG_MOVETO;
2107                 }
2108             }
2109 
2110             public int getWindingRule() {
2111                 return WIND_NON_ZERO;
2112             }
2113 
2114             public boolean isDone() {
2115                 return index >= ctrlpts.length;
2116             }
2117 
2118             public void next() {
2119                 index++;
2120             }
2121 
2122             public int currentSegment(float[] coords) {
2123                 if (isDone()) {
2124                     throw new NoSuchElementException("roundrect iterator out of bounds");
2125                 }
2126                 double ctrls[] = ctrlpts[index];
2127                 int nc = 0;
2128                 for (int i = 0; i < ctrls.length; i += 4) {
2129                     coords[nc++] = (float) (x + ctrls[i + 0] * w + ctrls[i + 1] * aw);
2130                     coords[nc++] = (float) (y + ctrls[i + 2] * h + ctrls[i + 3] * ah);
2131                 }
2132                 if (affine != null) {
2133                     affine.transform(coords, 0, coords, 0, nc / 2);
2134                 }
2135                 return types[index];
2136             }
2137 
2138             public int currentSegment(double[] coords) {
2139                 if (isDone()) {
2140                     throw new NoSuchElementException("roundrect iterator out of bounds");
2141                 }
2142                 double ctrls[] = ctrlpts[index];
2143                 int nc = 0;
2144                 for (int i = 0; i < ctrls.length; i += 4) {
2145                     coords[nc++] = x + ctrls[i + 0] * w + ctrls[i + 1] * aw;
2146                     coords[nc++] = y + ctrls[i + 2] * h + ctrls[i + 3] * ah;
2147                 }
2148                 if (affine != null) {
2149                     affine.transform(coords, 0, coords, 0, nc / 2);
2150                 }
2151                 return types[index];
2152             }
2153         }
2154     }
2155 }